package com.practice.springboot.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration // 声明这是一个配置类，用于定义 Spring 容器中的 Bean
@EnableWebSecurity // 启用 Spring Security 的 Web 安全支持
public class SecurityConfig {

    // 定义 SecurityFilterChain Bean，这是 Spring Security 5.4+ 推荐的配置方式
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 配置 HttpSecurity，定义安全策略
        http
                // 配置请求授权规则
                .authorizeHttpRequests(authz -> authz
                        // 匹配 "/admin" 路径的请求，要求用户具有 "ADMIN" 角色
                        // 原理：通过 AntPathRequestMatcher 匹配请求路径，然后检查用户的角色
                        // 注意事项：角色名通常以 "ROLE_" 前缀开头，但在配置时可以省略
                        .requestMatchers("/securityTest/admin").hasRole("ADMIN")
                        // 匹配 "/index" 路径的请求，要求用户具有 "ADMIN" 或 "USER" 角色
                        // access 方法允许使用更灵活的表达式来定义访问规则
                        .requestMatchers("/securityTest/public").hasRole("ADMIN")
                        .requestMatchers("/securityTest/public").hasRole("USER")

                        // 所有其他请求都要求用户已认证
                        .anyRequest().authenticated()
                )
                // 配置表单登录
                .formLogin(form -> form
                        // 指定自定义的登录页面
                        // 原理：当用户尝试访问受保护资源且未登录时，重定向到此登录页面
                        // 注意事项：登录页面必须包含用户名和密码的输入字段，通常名为 "username" 和 "password"
                        .loginPage("/securityTest/login")
                        // 允许所有用户访问登录页面
                        .permitAll()
                )
                // 配置登出功能
                .logout(logout -> logout
                        // 允许所有用户访问登出功能
                        .permitAll()
                )
                // 禁用 CSRF 保护（不推荐在生产环境中使用）
                // 原理：CSRF 保护用于防止跨站请求伪造攻击，禁用后将不再对请求进行 CSRF 验证
                // 注意事项：仅在开发环境中禁用，生产环境中应保持启用状态
                .csrf(AbstractHttpConfigurer::disable);

        // 构建并返回 SecurityFilterChain Bean
        return http.build();
    }

    // 定义 InMemoryUserDetailsManager Bean，用于管理内存中的用户详情
    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        // 创建 BCryptPasswordEncoder 实例，用于密码加密
        PasswordEncoder encoder = new BCryptPasswordEncoder();

        // 创建普通用户 "user"，密码为 "000"（加密后存储），角色为 "USER"
        // 原理：User.withUsername 方法创建 UserBuilder，设置用户名、密码和角色
        // 注意事项：密码会通过 encoder.encode 方法加密后存储
        UserDetails user = User.withUsername("user")
                .password(encoder.encode("000"))
                .roles("USER")
                .build();

        // 创建管理员用户 "admin"，密码为 "123"（加密后存储），角色为 "ADMIN" 和 "USER"
        UserDetails admin = User.withUsername("admin")
                .password(encoder.encode("123"))
                .roles("ADMIN", "USER")
                .build();

        // 返回 InMemoryUserDetailsManager，包含两个用户
        // 原理：InMemoryUserDetailsManager 是一个简单的内存用户存储实现
        // 注意事项：在生产环境中，通常会使用数据库或其他持久化存储方式
        return new InMemoryUserDetailsManager(user, admin);
    }

    // 定义 PasswordEncoder Bean，用于密码加密和验证
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 返回 BCryptPasswordEncoder 实例
        // 原理：BCrypt 是一种强大的密码哈希函数，专门设计用于密码存储
        // 注意事项：BCrypt 会自动生成盐值，并将其包含在加密后的密码中，因此每次加密相同密码会得到不同结果
        return new BCryptPasswordEncoder();
    }
}